package model;

import java.awt.GridLayout;
import java.util.ArrayList;

import javax.swing.JFrame;

import optimizers.commons.GAConstants;

import org.apache.commons.math3.ode.FirstOrderDifferentialEquations;
import org.apache.commons.math3.ode.nonstiff.AdamsMoultonIntegrator;
import org.apache.commons.math3.ode.nonstiff.ClassicalRungeKuttaIntegrator;
import org.apache.commons.math3.ode.nonstiff.GraggBulirschStoerIntegrator;
import org.apache.commons.math3.util.Pair;

import util.CalcErrEventHandler;
import util.MixingEventHandler;
import util.MyEventHandler;
import util.MyEventHandlerMultiCell;
import util.MyStepHandler;
import util.MyUtil;
import visualization.PlotExpData;

public class TicTacToeOligoSystemExplicitMix implements FirstOrderDifferentialEquations {

	public OligoSystemAdrian[][] table;
	public int width = 3;
	public int height = 3;
	public Input[] inputs = new Input[]{};
	public boolean correct = true;
	static public double leakFactor = 0.001;
	static public double leakThreshold = 1e-10;
	static public double fixedTemplatesValue = 20;
	static public final int offsetW = 0; //sequences are w v empty A B(always first)
	static public final int offsetV = 1;
	static public final int offsetEmpty = 2;
	static public final int offsetA = 3;
	static public final int offsetB = 4;
	
	public TicTacToeOligoSystemExplicitMix(int noOfSimpleSeq, double[][][] template,
			double[][] seqK, double[][] inhK, double[][] seqConcentration,
			double exo) {
		table = new OligoSystemAdrian[width][height];
		
		//NOTA BENE: we need sequence breaking stuff in the system: reserved sequences w and v
		
		double[][][] temp;
		
		Enzyme myExo = new Enzyme("exo", exo, Constants.exoKmSimple, Constants.exoKmInhib);
		
		for(int i=0;i<width;i++){
			for(int j=0;j<height;j++){
				
				temp = template.clone();
				temp[0] = template[0].clone();
				temp[0][0] = template[0][0].clone();
				temp[0][1] = template[0][1].clone();
				
				if(i==0 && j==0){ // add the w autocatalyst
					if(temp[0][0][0] < 1)
						temp[0][0][0] = TicTacToeOligoSystemExplicitMix.fixedTemplatesValue;// shouldn't diffuse
					temp[0][1][1] = 1e-20;
					table[i][j] = new OligoSystemAdrian(noOfSimpleSeq, temp.clone(), seqK, inhK, seqConcentration, myExo);
				} else if(i==width-1 && j==0){ // add the v autocatalyst
					if(temp[0][1][1] < 1)
						temp[0][1][1] = TicTacToeOligoSystemExplicitMix.fixedTemplatesValue; // shouldn't diffuse
					temp[0][0][0] = 1e-20;
					table[i][j] = new OligoSystemAdrian(noOfSimpleSeq, temp.clone(), seqK, inhK, seqConcentration, myExo);
				} else {
					temp[0][0][0] = temp[0][1][1] = 1e-20;
					table[i][j] = new OligoSystemAdrian(noOfSimpleSeq, temp.clone(), seqK, inhK, seqConcentration, myExo);
				}
				
			}
		}
		
	}

	@Override
	public void computeDerivatives(double t, double[] y, double[] ydot) {
		// y is the current state, linearized from the states of all OligoSystems in order for(width) for(height) (= height first)
		// first, update concentrations without leaks
		int offset = 0;
		int dim = table[0][0].getDimension();
		
		double[] partialDerivatives = new double[dim];
		for(int i = 0; i < this.width; i++){
			for(int j = 0; j < this.height; j++){
				table[i][j].computePartialDerivatives(t, offset, y, partialDerivatives);
				for(int k = 0; k < dim; k++){
					ydot[offset+k]=partialDerivatives[k];
				}
				offset += dim;
			}
		}
		
		//no leaks added implicitely
		//dotheharlemshake(y,ydot);
		
	}

	private ArrayList<Pair<Integer,Integer>> getNeighbours(int i,int j){
		ArrayList<Pair<Integer,Integer>> neighbours = new ArrayList<Pair<Integer,Integer>>();
		if(i > 0){ // there is a left neighbour
			neighbours.add(new Pair<Integer,Integer>(i-1,j));
		}
		if(i < width - 1){ // there is a right neighbour
			neighbours.add(new Pair<Integer,Integer>(i+1,j));
		}
		if(j > 0){ // there is a up neighbour
			neighbours.add(new Pair<Integer,Integer>(i,j-1));
		}
		if(j < height - 1){ // there is a down neighbour
			neighbours.add(new Pair<Integer,Integer>(i,j+1));
		}
		return neighbours;
	}

	@Override
	public int getDimension() {
		
		// We should have a value equal to n*m times the value of just one system
		return width*height*table[0][0].getDimension();
	}
	
	public double[][] calculateTimeSeries() {

		GraggBulirschStoerIntegrator myIntegrator = new GraggBulirschStoerIntegrator(
				1e-20, 10000, 1e-30, 1e-15);
		
		//ClassicalRungeKuttaIntegrator myIntegrator = new ClassicalRungeKuttaIntegrator(1e-2);
		
		//AdamsMoultonIntegrator myIntegrator = new AdamsMoultonIntegrator(10000, 1e-15, 10000, 1e-15, 1e-6);
		
		int dim = table[0][0].getDimension();
		MyEventHandler eventH = new MyEventHandlerMultiCell(dim,width,height);
		int offsetSpecialTemp1 = -1;//table[0][0].total+table[0][0].nbOfProtec+table[0][0].inhTotal+12*table[0][0].flatTemplates.indexOf(table[0][0].templates.get(0).get(0).get(0));
		int offsetSpecialTemp2 = -1; //table[0][0].total+table[0][0].nbOfProtec+table[0][0].inhTotal+12*table[0][0].flatTemplates.indexOf(table[0][0].templates.get(0).get(1).get(1));
		MixingEventHandler event = new MixingEventHandler(table[0][0].getDimension(),this.width,this.height,offsetSpecialTemp1,offsetSpecialTemp2);
		MyStepHandler handler = new MyStepHandler(table[0][0].total,
				eventH,this.height*this.width);
		CalcErrEventHandler eve = new CalcErrEventHandler();
		myIntegrator.addStepHandler(handler);
		myIntegrator.addEventHandler(eventH, 10000, 1e-6, 100);
		myIntegrator.addEventHandler(event, 3, 1e-6, 100);
		myIntegrator.addEventHandler(eve, 10000, 1-10, 1000);
		int offset = 0;
		
		double[] placeholder = new double[getDimension()];
		double[] temp;
		for(int i=0; i<this.width; i++){
			for(int j=0; j<this.height; j++){
				table[i][j].reinitializeOiligoSystem();
				temp = table[i][j].initialConditions();
				for(int k=0;k<dim;k++){
					try{
					placeholder[offset+k] = temp[k];
					} catch (ArrayIndexOutOfBoundsException e){
						System.out.println(""+(offset+k)+" "+k);
					}
				}
				offset += dim;
			}
		}
		try{
			//System.out.println("The door!");
		myIntegrator.integrate(this, 0, placeholder, model.Constants.numberOfPoints,
				placeholder);
		} catch (org.apache.commons.math3.exception.NumberIsTooSmallException exc) {
			
		}
		//System.out.println("Haha");
		// for (int t = 0; t < GAConstants.numberOfPoints; t++) {
		// where = 0;
		// outputModel.setInterpolatedTime(outputModel.getFinalTime()
		// * ((double) t / GAConstants.numberOfPoints));
		// //
		// System.out.println("value of the first sequence: "+outputModel.getInterpolatedState()[1]+" at time "+outputModel.getInterpolatedTime());
		// for (int i = 0; i < this.total; ++i) {
		// if (this.sequences.get(0).get(i) != null) {
		// series[where][t] = outputModel.getInterpolatedState()[i];
		// where++;
		// }
		// }
		// }
		this.inputs = eventH.inputs;
		this.correct = ((MyEventHandlerMultiCell) eventH).correct;
		return handler.getTimeSerie();
	}
	
	public static void main(String[] args){
		int nbSimple = 5; //Let's make a tristable qui joue
		double[][][] template = new double[6][5][5];
		template[0][2][2] = 5;
		template[0][3][3] = 5;
		template[0][4][4] = 5;
		template[3][3][3] = 10;
		template[3][4][4] = 10;
		template[4][2][2] = 10;
		template[4][4][4] = 10;
		template[5][2][2] = 10;
		template[5][3][3] = 10;
		double[][] seqK = new double[6][5];
		seqK[0][0] = 25;
		seqK[0][1] = 25;
		seqK[0][2] = 25;
		seqK[0][3] = 10;
		seqK[0][4] = 10;
		seqK[3][2] = 0.40;
		seqK[4][3] = 0.40;
		seqK[5][4] = 0.40;
		double[][] inhK = new double[6][5];
		inhK[3][2] = 1.2;
		inhK[4][3] = 1.2;
		inhK[5][4] = 0.45;
		double[][] seqConcentration = new double[6][5];
		seqConcentration[0][0] = 10;
		seqConcentration[0][1] = 10;
		seqConcentration[0][2] = 10;
		seqConcentration[0][3] = 1;
		seqConcentration[0][4] = 1;
		double exo = 1;
		
		TicTacToeOligoSystemExplicitMix bi = new TicTacToeOligoSystemExplicitMix( nbSimple,  template,
				seqK,  inhK,  seqConcentration, exo);

//		String test = "";
//		for(int i=0; i<3; i++){
//			for(int j= 0; j<3; j++){
//				test+= bi.getNeighbours(i, j).size()+"\t";
//			}
//			test+="\n";
//		}
//		
//		System.out.println("Size neighborhood: \n"+test);
		
		JFrame frame = new JFrame("");
		//frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(800, 600);
		double time1, time2;
		time1 = System.currentTimeMillis();
		double[][] time = bi.calculateTimeSeries();
		time2 = System.currentTimeMillis();
		System.out.println("done in "+(time2-time1));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setLayout(new GridLayout(3,3));
		double[][] separated = new double[5][];
		for(int i=0; i<3; i++){
			for(int j=0; j<3; j++){
				separated[0] = time[5*(i*3+j)];
				separated[1] = time[5*(i*3+j)+1];
				separated[2] = time[5*(i*3+j)+2];
				separated[3] = time[5*(i*3+j)+3];
				separated[4] = time[5*(i*3+j)+4];
				frame.add(new PlotExpData("TicTac"+(i*3+j),separated.clone()));
			}
		}
		//frame.pack();
		frame.setVisible(true);
	}

}
